package se.webbzon.boltzmann.editor.component.sequence;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Collection;

import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import se.webbzon.boltzmann.editor.component.AbstractComponent;
import se.webbzon.boltzmann.editor.component.dependencylist.DependencyComboBox;
import se.webbzon.boltzmann.editor.list.NullableComboBoxModel;
import se.webbzon.boltzmann.editor.mvc.EditorModel;
import se.webbzon.boltzmann.game.sequence.builder.AbstractSequenceBinding;
import se.webbzon.boltzmann.game.sequence.builder.AbstractSequenceRow;
import se.webbzon.boltzmann.game.sequence.builder.HardSequenceBinding;
import se.webbzon.boltzmann.game.sequence.builder.SequenceBuilder;
import se.webbzon.boltzmann.game.sequence.builder.SoftSequenceBinding;
import se.webbzon.oschi01.worldobject.WorldObject;

public class RowEditPanel extends AbstractComponent {
	
	// Auto generated serial version UID
	private static final long serialVersionUID = 670693895208292993L;

	// The sequence builder associated with this editor panel
	private final SequenceBuilder builder;
		
	// A collection of all listener to this panel
	private final Collection<RowEditListener> listeners;
	
	// The lists of this panel
	private final JList<AbstractSequenceRow> linkList;
		
	// The dynamic labels of this panel
	private final JLabel typeLabel;
		
	// The combo boxes of this panel
	private final DependencyComboBox<WorldObject> hardBinding;
	private final JComboBox<AbstractSequenceRow> linkCombo;
		
	// The buttons of this panel
	private final JButton linkApplyButton;
		
	// The text fields of this panel
	private final JTextField softBinding;
		
	// The currently selected row
	private AbstractSequenceRow row;
		
	// The currently selected link
	private int link;

	/*============================================================
	Constructors
	============================================================*/
	
	public RowEditPanel(EditorModel model, SequenceBuilder builder) {
		super("Sequence Row Editor");
		this.builder = builder;
		listeners = new ArrayList<RowEditListener>();
		setLayout(new BoxLayout(this,BoxLayout.Y_AXIS));
		
		// Create info panel
		JPanel infoPanel = new JPanel();
		infoPanel.setLayout(new BorderLayout());
		infoPanel.setBorder(BorderFactory.createTitledBorder("Info:"));
	
		typeLabel = new JLabel();	
		infoPanel.add(typeLabel,BorderLayout.WEST);
		setTypeLabel(null);
		
		add(infoPanel);

		// Create link panel
		JPanel linkPanel = new JPanel();
		linkPanel.setLayout(new BorderLayout());
		linkPanel.setBorder(BorderFactory.createTitledBorder("Links:"));

		JPanel linkListPanel = new JPanel();
		linkListPanel.setLayout(new BorderLayout());
		
		linkList = new JList<AbstractSequenceRow>();
		linkList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		linkList.addListSelectionListener(new ListSelectionListenerImpl());
		linkList.setModel(new DefaultListModel<AbstractSequenceRow>());
		linkList.setCellRenderer(new SequenceRowListCell());
		JScrollPane linkScrollPane = new JScrollPane(linkList);
		linkScrollPane.setPreferredSize(new Dimension(256,128));
		linkListPanel.add(linkScrollPane,BorderLayout.CENTER);

		JPanel linkButtonPanel = new JPanel();
		linkButtonPanel.setLayout(new BorderLayout());
		linkButtonPanel.add(new JButton(new AddLinkAction()),BorderLayout.WEST);
		linkButtonPanel.add(new JButton(new RemoveLinkAction()),BorderLayout.EAST);
		linkListPanel.add(linkButtonPanel,BorderLayout.SOUTH);
		linkPanel.add(linkListPanel,BorderLayout.WEST);

		linkPanel.add(Box.createHorizontalStrut(32),BorderLayout.CENTER);

		JPanel linkConfigPanel = new JPanel();
		linkConfigPanel.setLayout(new BorderLayout());
		JPanel linkApplyPanel = new JPanel();
		linkApplyPanel.setLayout(new BoxLayout(linkApplyPanel,BoxLayout.X_AXIS));
		linkApplyPanel.add(new JLabel("Link: "));
		linkCombo = new JComboBox<AbstractSequenceRow>();
		linkCombo.setModel(new NullableComboBoxModel<AbstractSequenceRow>());
		linkCombo.setRenderer(new SequenceRowListCell());
		linkCombo.setPreferredSize(new Dimension(256,-1));
		linkApplyPanel.add(linkCombo);
		linkApplyButton = new JButton(new ApplyLinkAction());
		linkApplyPanel.add(linkApplyButton);
		linkConfigPanel.add(linkApplyPanel,BorderLayout.NORTH);
		linkPanel.add(linkConfigPanel,BorderLayout.EAST);
		add(linkPanel);
		
		// Created binding panel
		JPanel bindingPanel = new JPanel();
		bindingPanel.setLayout(new GridLayout(3,2));
		bindingPanel.setBorder(BorderFactory.createTitledBorder("Bindings:"));

		bindingPanel.add(new JLabel("Hard Binding:"));
		hardBinding = 
				new DependencyComboBox<WorldObject>(model,WorldObject.class);
		bindingPanel.add(hardBinding);

		bindingPanel.add(new JLabel("Soft Binding:"));
		softBinding = new JTextField();
		bindingPanel.add(softBinding);

		bindingPanel.add(Box.createGlue());
		bindingPanel.add(new JButton(new ApplyBindingAction()));

		add(bindingPanel);

		editRow(null);
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/** Adds a listener to this row editor panel. **/
	public boolean addListener(RowEditListener listener) {
		return listeners.add(listener);
	}
	
	/** Removes a listener from this row editor panel. **/
	public boolean removeListener(RowEditListener listener) {
		return listeners.remove(listener);
	}
	
	/** Clears all the listeners in this row editor panel. **/
	public void clearListener() {
		listeners.clear();
	}
	
	/** Returns the currently selected sequence row. If no row is selected 
	 * then null is returned. **/
	public AbstractSequenceRow getSelectedRow() {
		return row;
	}
	
	/** Returns the currenyly selected link for the currently selected
	 * sequence row. If no link is selected then -1 is returned. **/
	public int getSelectedLink() {
		return link;
	}
	
	/** Starts the editing of the given row. If the row does not exist
	 * in the sequence builder provided by the constructor then the 
	 * current row is closed and all editable fields will be disabled. **/
	public void editRow(AbstractSequenceRow row) {
		for (RowEditListener listener : listeners.toArray(new RowEditListener[0]))
			listener.editRow(this, row);
		
		if (row == null || !builder.containsRow(row)) {
			this.row = null;
			
			// Clear bindings
			hardBinding.setInstance(null);
			softBinding.setText("");
			
			// Clear link list
			DefaultListModel<AbstractSequenceRow> linkModel =
					(DefaultListModel<AbstractSequenceRow>) linkList.getModel();
			linkModel.clear();
			NullableComboBoxModel<AbstractSequenceRow> linkComboModel =
					(NullableComboBoxModel<AbstractSequenceRow>) linkCombo.getModel();
			linkComboModel.setSelectedItem(null);

			// Disable components
			hardBinding.setEnabled(false);
			softBinding.setEnabled(false);
			linkList.setEnabled(false);
			linkCombo.setEnabled(false);
			setTypeLabel(null);
			
		} else if (this.row != row) {
			this.row = row;
			
			// Read binding
			AbstractSequenceBinding binding = row.getBinding();
			if (binding != null) {
				if (binding.isHard()) {
					hardBinding.setInstance(binding.getHard());
					softBinding.setText("");
				} else {
					hardBinding.setInstance(null);
					softBinding.setText(binding.getSoft());
				}
			} else {
				hardBinding.setInstance(null);
				softBinding.setText("");
			}

			// Read links to list
			DefaultListModel<AbstractSequenceRow> linkModel =
					(DefaultListModel<AbstractSequenceRow>) linkList.getModel();
			linkModel.clear();
			for (int i = 0; i < row.links(); i++) {
				Integer link = row.getLink(i);
				if (link == null)
					linkModel.addElement(null);
				else
					linkModel.addElement(builder.getRow(link));
			}

			// Read links to combo
			NullableComboBoxModel<AbstractSequenceRow> linkComboModel =
					(NullableComboBoxModel<AbstractSequenceRow>) linkCombo.getModel();
			linkComboModel.clearElements();
			for (int i = 0; i < builder.rows(); i++)
				linkComboModel.addElement(builder.getRow(i));

			// Enable components
			hardBinding.setEnabled(true);
			softBinding.setEnabled(true);
			linkList.setEnabled(true);
			setTypeLabel(row);
			editLink(-1);
		}
	}
	
	/** Starts the editing of the link with the given index in the currently
	 * selected row specified by the method getSelectedRow(). If the
	 * link does not exists then the current link is closed and all 
	 * editable fields will be disabled. **/
	public void editLink(int index) {
		if (index >= 0 && index < linkList.getModel().getSize()) {
			// Select the link if it is not already selected
			if (linkList.getSelectedIndex() != index)
				linkList.setSelectedIndex(index);
			
			if (link != index) {
				link = index;
				
				linkCombo.setSelectedItem(linkList.getSelectedValue());
				linkCombo.setEnabled(true);
			}
		} else {
			// Clear selection if it is not already clear
			if (linkList.getSelectedIndex() > 0)
				linkList.clearSelection();
			
			link = -1;

			NullableComboBoxModel<AbstractSequenceRow> linkComboModel =
					(NullableComboBoxModel<AbstractSequenceRow>) linkCombo.getModel();
			linkComboModel.setSelectedItem(null);
			linkCombo.setEnabled(false);
		}
	}
	
	/*============================================================
	Private Methods
	============================================================*/
	
	/** Sets the text of the type label. **/
	private void setTypeLabel(AbstractSequenceRow row) {
		if (row == null)
			typeLabel.setText("Type: -");
		else
			typeLabel.setText("Type: " + row.getName());
	}
	
	/** Notifies to all the listener that a change has been made to
	 * the current row in the editor. **/
	private void notifyChange() {
		for (RowEditListener listener : listeners.toArray(new RowEditListener[0]))
			listener.changeRow(this, row);
	}
	
	/*============================================================
	Private Classes
	============================================================*/
	
	/** A selection listeners used to listen to selections in the link list. **/
	private final class ListSelectionListenerImpl implements ListSelectionListener {

		@Override public void valueChanged(ListSelectionEvent event) {
			int index = linkList.getSelectedIndex();
			if (index >= 0)
				editLink(index);
		}
		
	}
	
	/** An abstract action used for the apply binding button. **/
	private final class ApplyBindingAction extends AbstractAction {
		
		// Auto generated serial version UID
		private static final long serialVersionUID = -3065110862198253196L;
		
		/*============================================================
		Constructor
		============================================================*/

		public ApplyBindingAction() {
			putValue(AbstractAction.NAME,"Apply");
		}

		/*============================================================
		Public Methods
		============================================================*/
		
		@Override public void actionPerformed(ActionEvent arg0) {
			if (row != null) {
				WorldObject hard = hardBinding.getInstance();
				if (hard == null) {
					String soft = softBinding.getText();
					if (soft.isEmpty())
						row.setBinding(null);
					else
						row.setBinding(new SoftSequenceBinding(soft));
				} else
					row.setBinding(new HardSequenceBinding(hard));
				
				// Notify change
				notifyChange();
			}
		}
		
	}
	
	/** An abstract action used for the apply link button. **/
	private final class ApplyLinkAction extends AbstractAction {
		
		// Auto generated serial version UID
		private static final long serialVersionUID = 4158032716308873420L;
		
		/*============================================================
		Constructor
		============================================================*/

		public ApplyLinkAction() {
			putValue(AbstractAction.NAME,"Apply");
		}

		/*============================================================
		Public Methods
		============================================================*/
		
		@Override public void actionPerformed(ActionEvent event) {
			if (row != null) {
				int linkIndex = getSelectedLink();
				if (linkIndex >= 0) {
					int newLink = linkCombo.getSelectedIndex();
					newLink = (newLink < 0) ? 0 : newLink;
					
					AbstractSequenceRow newRow = linkCombo.getItemAt(newLink);
					
					// Update sequence row
					if (newLink == 0)
						row.setLink(linkIndex, null);
					else
						row.setLink(linkIndex, newLink-1);
					
					// Update link list
					DefaultListModel<AbstractSequenceRow> linkModel =
							(DefaultListModel<AbstractSequenceRow>) linkList.getModel();
					linkModel.set(linkIndex, newRow);
					
					// Notify change
					notifyChange();
				}
			}
		}
		
	}
	
	/** An abstract action used for the add link button. **/
	private final class AddLinkAction extends AbstractAction {
		
		// Auto generated serial version UID
		private static final long serialVersionUID = -89484511149486375L;
		
		/*============================================================
		Constructor
		============================================================*/

		public AddLinkAction() {
			putValue(AbstractAction.NAME,"Add");
		}

		/*============================================================
		Public Methods
		============================================================*/
		
		@Override public void actionPerformed(ActionEvent event) {
			if (row != null && row.addLink(null)) {
				DefaultListModel<AbstractSequenceRow> linkModel =
						(DefaultListModel<AbstractSequenceRow>) linkList.getModel();
				linkModel.addElement(null);
				editLink(linkModel.getSize()-1);
				
				// Notify change
				notifyChange();
			}
		}
		
	}
	
	/** An abstract action used for the add link button. **/
	private final class RemoveLinkAction extends AbstractAction {
		
		// Auto generated serial version UID
		private static final long serialVersionUID = -3128647647817955405L;
		
		/*============================================================
		Constructor
		============================================================*/

		public RemoveLinkAction() {
			putValue(AbstractAction.NAME,"Remove");
		}

		/*============================================================
		Public Methods
		============================================================*/
		
		@Override public void actionPerformed(ActionEvent event) {
			int index = linkList.getSelectedIndex();
			if (row != null && index >= 0 && row.removeLink(index)) {
				DefaultListModel<AbstractSequenceRow> linkModel =
						(DefaultListModel<AbstractSequenceRow>) linkList.getModel();
				linkModel.remove(index);
				
				// Notify change
				notifyChange();
			}
		}
		
	}

}
